import * as Cesium from 'cesium'
import type { Viewer } from 'cesium'
import { WindCore, assign, defaultOptions, Field, formatData, isArray, removeDomNode } from 'wind-core'
import type { IGFSItem } from 'wind-core'

class CesiumWind {
  private canvas: HTMLCanvasElement | null
  private wind: WindCore | undefined
  private field: Field | undefined
  private viewer: Viewer | undefined
  private options

  constructor(data: IGFSItem[], options = {}) {
    this.options = assign({}, options)
    this.pickWindOptions()
    const canvas = document.createElement('canvas')
    canvas.style.cssText = 'position:absolute; left:0; top:0;user-select:none;pointer-events: none;'
    canvas.className = 'cesium-wind-j'
    this.canvas = canvas
    if (data) this.setData(data)
  }

  public addTo(viewer: Viewer) {
    this.viewer = viewer
    this.appendCanvas()
    if (this.canvas) this.render(this.canvas)
  }

  public show() {
    if (!this.viewer) return
    if (this.wind) this.wind.start()
    if (this.canvas && this.canvas.parentElement) this.canvas.style.display = 'block'
  }

  public hide() {
    if (!this.viewer) return
    if (this.wind) this.wind.stop()
    if (this.canvas && this.canvas.parentElement) this.canvas.style.display = 'none'
  }

  public removeLayer() {
    if (!this.viewer) return
    if (this.wind) this.wind.stop()
    if (this.canvas) removeDomNode(this.canvas)
    if (this.canvas && this.canvas.parentElement) this.canvas.parentElement.removeChild(this.canvas)
    this.canvas = null
  }

  private pickWindOptions() {
    Object.keys(defaultOptions).forEach((key) => {
      if (key in this.options) {
        if (this.options.windOptions === undefined) {
          this.options.windOptions = {}
        }
        this.options.windOptions[key] = this.options[key]
      }
    })
  }

  private setData(data: IGFSItem[]) {
    if (isArray(data)) this.field = formatData(data)
    if (this.viewer && this.canvas && this.field) {
      this.wind?.updateData(this.field)
      this.appendCanvas()
      this.render(this.canvas)
    }
  }

  private appendCanvas() {
    if (!this.viewer || !this.canvas) return
    if (document.querySelector('.cesium-wind-j')) return
    this.adjustSize()
    const cesiumWidget = this.viewer.canvas.parentNode
    cesiumWidget?.appendChild(this.canvas)
  }

  private adjustSize() {
    if (!this.viewer) return
    const { width, height } = this.viewer.canvas
    const devicePixelRatio = 1
    if (this.canvas) {
      this.canvas.width = width * devicePixelRatio
      this.canvas.height = height * devicePixelRatio
      this.canvas.style.width = width + 'px'
      this.canvas.style.height = height + 'px'
    }
  }

  private render(canvas: HTMLCanvasElement) {
    if (!this.field || !this.viewer) return
    const opt = this.options.windOptions || {}
    if (canvas && !this.wind) {
      const ctx = this.canvas?.getContext('2d')
      if (ctx) {
        this.wind = new WindCore(ctx, opt, this.field)
        this.wind.project = this.project.bind(this)
        this.wind.unproject = this.unproject.bind(this)
        this.wind.intersectsCoordinate = this.intersectsCoordinate.bind(this)
        this.wind.postrender = () => {}
      }
    }
    this.wind?.prerender()
    this.wind?.render()
  }

  private project(coordinate: [number, number]): [number, number] | null {
    const [longitude, latitude] = coordinate
    if (!this.viewer) return null
    const position = Cesium.Cartesian3.fromDegrees(longitude, latitude)
    const sceneCoor = Cesium.SceneTransforms.worldToWindowCoordinates(this.viewer.scene, position)
    return sceneCoor ? [sceneCoor.x, sceneCoor.y] : null
  }

  private unproject(pixel: [number, number]): [number, number] | null {
    if (!this.viewer) return null
    const pick = new Cesium.Cartesian2(...pixel)
    const cartesian = this.viewer.scene.globe.pick(this.viewer.camera.getPickRay(pick)!, this.viewer.scene)
    if (!cartesian) return null
    const ellipsoid = this.viewer.scene.globe.ellipsoid
    const cartographic = ellipsoid.cartesianToCartographic(cartesian)
    const lat = Cesium.Math.toDegrees(cartographic.latitude)
    const lng = Cesium.Math.toDegrees(cartographic.longitude)
    return [lng, lat]
  }

  /**
   * @description 判断点位是否在当前相机视角下
   * @param coordinate 坐标点位
   * @returns
   */
  private intersectsCoordinate(coordinate: [number, number]): boolean {
    if (!this.viewer) return false
    const ellipsoid = Cesium.Ellipsoid.WGS84
    const camera = this.viewer.camera
    // 使用 any 的原因：
    // @from: https://community.cesium.com/t/ellipsoidaloccluder-is-missing-what-is-the-replacement/28784
    // eslint-disable-next-line @typescript-eslint/no-explicit-any
    const occluder = new (Cesium as any).EllipsoidalOccluder(ellipsoid, camera.position)
    const point = Cesium.Cartesian3.fromDegrees(...coordinate)
    return occluder.isPointVisible(point)
  }
}

const WindLayer = CesiumWind

export { Field, WindLayer }

export default CesiumWind
